Skip to content

W85 — The Resort Tells You What Needs Attention - #361

Merged
The-Running-Dev merged 3 commits into
mainfrom
slice/W85
Aug 21, 2026
Merged

W85 — The Resort Tells You What Needs Attention#361
The-Running-Dev merged 3 commits into
mainfrom
slice/W85

Conversation

@The-Running-Dev

@The-Running-Dev The-Running-Dev commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #356. Builds system 19 (alerts), the last of the five stub world-graph tick systems named in design/90-decisions.md's 2026-08-05 known-and-retained entry.

  • Achievements (W85.1): still-locked achievements are evaluated by definition id, in the campaign's own canonical (lexicographic) order, against post-resolution state. An unlock writes unlockedAchievementIds.<id>.exists under the core achievement_unlocked reason. Profile mirroring is the existing generic session-store mechanism (upsertAchievements), which only ever runs after a whole action succeeds — proved by a run whose refused advance_ticks leaves the profile untouched.
  • Alerts (W85.2–W85.4): exactly three closed families — active incident, broken building, scenario resolved — each keyed on a semantic key built only from the family name plus a published id, never authored text. The two kind-owned families resolve through new world-graph.alert.<type>.title|message built-in strings; incident alerts reuse the incident definition's own name/description keys.
  • No duplicates, no leakage (W85.3, W85.5): a source already represented by an uncleared alert never raises a second one (dismissal doesn't free the key — only clearing does); alerts and achievements feed nothing back into the same tick (tested by dropping both before system 20 and diffing the rest of state).
  • Audit and events (W85.6): achievement.unlocked, alert.raised, alert.cleared are declared and emitted; alert creation/removal audit rows are hidden (visible: false) per contract.
  • Determinism (W85.7): advance_ticks n serializes byte-identically whether submitted whole or split, across a batch that both raises and clears an alert.
  • Fixture (W85.8): a new world-graph-mvp-achievement replay fixture proves a real unlock (double-cleaner, gated on hiring two cleaners — inert everywhere else, following the same gating pattern W84's storm incident used). All previously committed outcomes are unchanged byte-for-byte.
  • Decision ledger (W85.9): closes out 90-decisions.md's tick-system entry — it also picked up W84's missed amendment for incidents (system 16), so the entry now correctly reads zero systems remaining rather than jumping straight from "two remain" to a nonexistent state.

A resolved contract-vs-code disagreement

20-contract.md §13 specifies the achievement audit path as unlockedAchievementIds.<id>.exists (world-graph's own member-scoped path shape), but the generic session-store matcher (achievementIdFrom in src/engine/src/core/session/store.ts) only recognized achieved.<id> (story-graph's flat convention). Implementing the contract literally would have silently broken profile mirroring for world-graph, with no test in this slice's own file likely to catch it. Resolved (user-confirmed) by widening the matcher to accept both prefixes — each kind keeps its own contract-literal path shape.

Follow-up commits

  • d347ba5 — code review fixes: alerts()'s achievement-unlock and alert-clear loops moved to Set-driven passes (matching the pattern story-graph/achievements.ts already established), and reasons.ts's deliberately-unregistered list now names alert_raised/alert_cleared alongside the other visible: false reasons.
  • bfdf226 — regenerated docs/docs/engine/12-world-graph-kind.md and docs/docs/guide.md from design/20-contract.md's §12 edit, which the initial commit had made without carrying through the generation workflow.

Touches

  • src/engine/src/kinds/world-graph/tick/pipeline.ts — system 19 (alerts)
  • src/engine/src/kinds/world-graph/kind.tseventNames
  • src/engine/src/kinds/world-graph/reasons.ts — kind-owned alert strings
  • src/engine/src/campaigns/world-graph-mvp.ts — the double-cleaner achievement
  • src/engine/src/core/session/store.ts — widened achievement path matcher
  • New fixture pair under src/engine/fixtures/replay/
  • design/20-contract.md, design/90-decisions.md — descriptive corrections (§12 event-table status rows for the three delivered events; the tick-system register closed out)
  • docs/docs/engine/12-world-graph-kind.md, docs/docs/guide.md — regenerated from the design/20-contract.md edit above

Verified

Ran and passed:

  • Typecheck — npm --prefix src/engine run typecheck (tsc --noEmit && tsc --noEmit -p tsconfig.scripts.json) — exit 0
  • Lint — npm --prefix src/engine run lint (eslint src scripts) — exit 0
  • Test — npm --prefix src/engine test (vitest run) — Test Files 79 passed (79), Tests 1146 passed (1146)
  • Pack package — npm pack --silent produced the-running-dev-game-engine-0.10.0.tgz, exit 0
  • Inspect tarball — no src/, no tsconfig*.json, no .test.* build artifacts; package/dist/ present
  • Consumer smoke — install:engine && build && smoke all exited 0
  • Parse-check PowerShell scripts — every *.ps1 parsed with 0 errors
  • Run Pester tests — 235 passed, 0 failed, 18 skipped
  • Validate Markdown links, terminology, and generated files — ./build/Test-Documentation.ps1: human-documentation drift check passed across 18 generated engine page(s), compatibility pointers, and the guide; documentation checks passed across 123 Markdown file(s)
  • Build and verify landing page — npm --prefix site run check (format:check, lint, typecheck, test, test:browser via Playwright, test:build, test:merge) — all passed

Did not run locally (each has a corresponding CI check on this PR, all green — Build, run, and smoke the image, Verify Documentation Build):

  • Build documentation — needs the docs-template container image; docs.ps1 (the local wrapper) is absent from this checkout per CLAUDE.md (installer-generated, not committed).
  • Merge landing page into documentation build — depends on the docs build above.
  • Test the host / Positive route and probe smoke / Negative fixture — need NUGET_GITHUB_TOKEN to restore SubZeroDev.Platform.Hosting from the sibling repository's GitHub Packages feed; not set locally.

Actual PR checks (confirming the above): engine pass, powershell pass, Documentation links and terminology pass, Verify Documentation Build pass, Build, run, and smoke the image pass.

Test plan

  • Unit: achievement unlock, non-re-unlock, all three alert families, key derivation, no-duplicate/clear cycle, audit visibility, no-downstream-leakage (pipeline.test.ts)
  • Integration: profile mirroring on success vs. refusal, byte-identical batch split across a raise/clear cycle (kind.test.ts)
  • Replay: existing 6 fixtures unchanged; new world-graph-mvp-achievement fixture matches its committed outcome

Builds system 19 (alerts): still-locked achievements unlock against
post-resolution state first, then the three closed alert families —
active incident, broken building, scenario resolved — derive from the
current tick's own source set, raising once and clearing when a source
goes inactive. Closes the world-graph tick-system gap 90-decisions.md
has tracked since 2026-08-05 (W84 also gets its missed amendment).

Widens the session store's achievement path matcher to accept
world-graph's own member-scoped `unlockedAchievementIds.<id>.exists`
alongside story-graph's flat `achieved.<id>` — 20-contract.md's §13
literally specifies the former, which the matcher never recognized.

New replay fixture (world-graph-mvp-achievement) proves an achievement
actually unlocks; every previously committed outcome is unchanged.
Two O(n^2)-shaped patterns in System 19's alerts() regressed against
the Set-based fixes already established elsewhere in this codebase:
the achievement-unlock loop used array .includes()/spread instead of
a Set (story-graph/achievements.ts already fixed this exact pattern
per PR #51), and the alert-clear loop remapped the whole alerts array
once per clear instead of a single Set-driven pass (matching
effects.ts's resolve_incident). Also names the two new visible:false
reasons (alert_raised, alert_cleared) in reasons.ts's existing
deliberately-unregistered list, so a later flip to visible:true isn't
missed the way the identical defect was missed before.
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts
Comment thread design/20-contract.md
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts
Comment thread src/engine/src/core/session/store.ts
Comment thread src/engine/src/kinds/world-graph/reasons.ts
Comment thread src/engine/src/kinds/world-graph/tick/pipeline.ts
design/20-contract.md's event-status table edit (achievement.unlocked
and the two alert events moved from "specified, not yet delivered" to
"delivered") needed the generation workflow run through: this
regenerates docs/docs/engine/12-world-graph-kind.md and
docs/docs/guide.md, and records build/Test-Documentation.ps1 now
passing in verify-report.json (previously Failed, per code review on
this PR).
@The-Running-Dev
The-Running-Dev merged commit 3ea878e into main Aug 21, 2026
14 checks passed
@The-Running-Dev
The-Running-Dev deleted the slice/W85 branch August 21, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

W85 — The Resort Tells You What Needs Attention

1 participant